using System;
using System.IO;
using System.Net;
using System.Reflection;
using System.Windows;
using System.Windows.Resources;
using System.Xml;

namespace ClientPreloader
{
	public class XapLoader
	{
		public event EventHandler<XapLoaderEventArgs> XapLoaded;
		public event EventHandler<DownloadProgressChangedEventArgs> DownloadProgressChanged;
		private string _rootAssembly;
		private string _typeName;

		public void LoadXap(Uri xapUri, string typeName)
		{
			_rootAssembly = Path.GetFileNameWithoutExtension(xapUri.ToString());
			_typeName = typeName;

			WebClient wc = new WebClient();
			wc.OpenReadCompleted += WcOpenReadCompleted;
			wc.DownloadProgressChanged += WcDownloadProgressChanged;
			wc.OpenReadAsync(xapUri);
		}

		void WcDownloadProgressChanged(object sender, DownloadProgressChangedEventArgs e)
		{
			if (DownloadProgressChanged != null)
				DownloadProgressChanged(this, e);
		}

		private void WcOpenReadCompleted(object sender, OpenReadCompletedEventArgs e)
		{
			var manifestStream = Application.GetResourceStream(
				new StreamResourceInfo(e.Result, null),
				new Uri("AppManifest.xaml", UriKind.Relative));

			string appManifest = new StreamReader(manifestStream.Stream).ReadToEnd();
			string assemblyName = _rootAssembly + ".dll";
			XmlReader reader = XmlReader.Create(new StringReader(appManifest));
			Assembly asm = null;
			while (reader.Read())
			{
				if (reader.IsStartElement("AssemblyPart"))
				{
					reader.MoveToAttribute("Source");
					reader.ReadAttributeValue();
					if (reader.Value == assemblyName)
					{
						var assemblyStream = new StreamResourceInfo(e.Result, "application/binary");
						var si = Application.GetResourceStream(assemblyStream, new Uri(reader.Value, UriKind.Relative));
						AssemblyPart p = new AssemblyPart();
						asm = p.Load(si.Stream);
						break;
					}
				}
			}

			if (asm == null)
				throw new InvalidOperationException("Could not find specified assembly.");

			var o = asm.CreateInstance(_typeName);
			if (o == null)
				throw new InvalidOperationException("Could not create instance of requested type.");

			RaiseXapLoadedEvent(o);
		}

		private void RaiseXapLoadedEvent(object instance)
		{
			if (XapLoaded != null)
			{
				XapLoaded(this, new XapLoaderEventArgs(instance));
			}
		}
	}

	public class XapLoaderEventArgs : EventArgs
	{
		public object Instance { get; set; }

		public XapLoaderEventArgs(object instance)
		{
			Instance = instance;
		}
	}
}